﻿namespace Chalk.Extension
{
    #region Directives

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Text;
    using Graffiti.Core;
    using DataBuddy;

    #endregion

    /// <summary>
    /// A collection of useful helper methods that extend the Graffiti CMS system.
    /// </summary>
    /// <remarks>
    /// To use these helper methods, compile the solution, add the ChalkExtensions.Chalk.Extension.dll to the \bin
    /// directory of your Graffiti directory, then within your .view templates, reference the Chalk Extension
    /// using the following syntax (N.B. the $ in "$ChalkExtensions"):
    /// <code>$ChalkExtensions.GetAllPostsForCategory("Archive")</code>
    /// </remarks>
    [Chalk("ChalkExtensions")]
    public class ChalkExtensions
    {
        #region Public Methods

        /// <summary>
        /// Returns all posts for the specified category
        /// </summary>
        /// <param name="categoryName">Name of the category which contains posts to be retrieved</param>
        /// <returns>Collection of matching posts</returns>
        /// <remarks>
        /// Usage:
        /// <code>
        /// #set($allEventsPosts = $ChalkExtensions.GetAllPostsForCategory("Events"))
        /// #foreach($post in $allEventsPosts)
        ///	    <div class="contenttextblock bottom">
        ///         <h3><a href="$post.Url">$post.Title</a></h3>
        ///         <p>
        ///			    $post.Excerpt("", "" , "[read more]", 300)
        ///			</p>
        ///     </div>
        ///	#end
        /// </code>
        /// </remarks>
        public PostCollection GetAllPostsForCategory(string categoryName)
        {
            PostCollection posts = ZCache.Get<PostCollection>(string.Format("ChalkExtensions::GetAllPostsForCategory::categoryName:{0}", new object[] {categoryName}));

            if (posts == null)
            {
                posts = new PostCollection();
                Data data = new Data();
                Category category = data.GetCategory(categoryName);

                if (category != null)
                {
                    Query query = PostCollection.DefaultQuery(category.SortOrder);

                    if (Category.IncludeChildPosts)
                    {
                        if (category.ParentId > 0)
                        {
                            query.AndWhere(Post.Columns.CategoryId, category.Id);
                        }
                        else
                        {
                            List<int> list = new List<int>(category.Children.Count + 1);

                            category.Children.ForEach(delegate(Category childCategory) { list.Add(childCategory.Id); });

                            list.Add(category.Id);

                            query.AndInWhere(Post.Columns.CategoryId, list.ToArray());
                        }
                    }
                    else
                    {
                        query.AndWhere(Post.Columns.CategoryId, category.Id);
                    }

                    posts.LoadAndCloseReader(query.ExecuteReader());

                    ZCache.InsertCache(string.Format("ChalkExtensions::GetAllPostsForCategory::categoryName:{0}", new object[] {categoryName}), posts, 60);
                }
            }

            return posts;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="categoryName"></param>
        /// <returns></returns>
        public string GetAllTagsForCategory(string categoryName)
        {
            Debug.WriteLine(string.Format("ChalkExtensions::GetAllTagsForCategory::categoryName:{0}", categoryName));
            string tags = ZCache.Get<string>(string.Format("ChalkExtensions::GetAllTagsForCategory::categoryName:{0}", new object[] { categoryName }));

            if (tags == null)
            {
                PostCollection posts = this.GetAllPostsForCategory(categoryName);

                tags = GetAllTagsFromPosts(posts);

                if (tags != null)
                {
                    ZCache.InsertCache(string.Format("ChalkExtensions::GetAllTagsForCategory::categoryName:{0}", new object[] { categoryName }), tags, 60);
                }
                else
                {
                    tags = string.Empty;
                }
            }

            return tags;
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="posts"></param>
        /// <returns></returns>
        public string GetAllTagsFromPosts(PostCollection posts)
        {
            string cacheKey = this.GetCacheKey(posts);

            string tags = ZCache.Get<string>(string.Format("ChalkExtensions::GetAllTagsForCategory::posts:{0}", new object[] { cacheKey }));

            if (tags == null)
            {
                Dictionary<string, string> uniqueTagList = new Dictionary<string, string>();
                List<string> tagList = new List<string>();

                StringBuilder sb = new StringBuilder();

                Macros macros = new Macros();

                foreach (Post post in posts)
                {
                    if (post.TagList == null)
                    {
                        post.TagList = string.Empty;
                        post.Save();
                    }

                    if (post.TagList.Length > 0)
                    {
                        sb.Append(post.TagList);
                        sb.Append(",");
                    }
                }

                string[] rawTagArray = sb.ToString().Split(',');

                foreach (string tag in rawTagArray)
                {
                    if (!tagList.Contains(tag) && tag.Length > 0)
                    {
                        tagList.Add(tag);
                    }
                }

                tagList.Sort(delegate(string x, string y) { return String.Compare(x, y); });

                string sortedTags = string.Join(",", tagList.ToArray());

                try
                {
                    sortedTags = macros.TagList(sortedTags);
                }
                catch(Exception exception)
                {
                    
                }

                if (sortedTags.Length > 10)
                {
                    tags = sortedTags.Substring(11);
                }

                if (tags != null)
                {
                    ZCache.InsertCache(string.Format("ChalkExtensions::GetAllTagsForCategory::posts:{0}", new object[] { cacheKey }), tags, 60);
                }
            }

            return tags;
        }

        /// <summary>
        /// Retrieves the next post in the category
        /// </summary>
        /// <param name="categoryName">Category to retrieve post from</param>
        /// <param name="currentPost">Current Post</param>
        /// <returns>The next post in the specified category collection, to the post specified</returns>
        /// <remarks>
        /// Usage:
        /// <code>#set($nextPost = $ChalkExtensions.GetNextPostForCategory("Archive", $currentPost))</code>
        /// </remarks>
        public Post GetNextPostForCategory(string categoryName, Post currentPost)
        {
            return GetNextPreviousPostForCategory(categoryName, currentPost, true);
        }

        /// <summary>
        /// Retrieves the previous post in the category
        /// </summary>
        /// <param name="categoryName">Category to retrieve post from</param>
        /// <param name="currentPost">Current Post</param>
        /// <returns>The previous post in the specified category collection, to the post specified</returns>
        /// <remarks>
        /// Usage:
        /// <code>#set($previousPost = $ChalkExtensions.GetPreviousPostForCategory("Archive", $currentPost))</code>
        /// </remarks>
        public Post GetPreviousPostForCategory(string categoryName, Post currentPost)
        {
            return GetNextPreviousPostForCategory(categoryName, currentPost, false);
        }

        /// <summary>
        /// Gets the specified number of posts randomly from the category specified.
        /// </summary>
        /// <param name="categoryName">Name of the category which contains posts to be retrieved</param>
        /// <param name="numberOfPosts">Number of posts to return</param>
        /// <returns>Collection of randomly selected Posts from the specified category</returns>
        /// <remarks>
        /// Usage:
        /// <code>#set($fiveRandomArchivePosts = $ChalkExtensions.GetRandomPostsForCategory("Archive"), 5)</code>
        /// </remarks>
        public PostCollection GetRandomPostsForCategory(string categoryName, int numberOfPosts)
        {
            return Randomize(this.GetAllPostsForCategory(categoryName), numberOfPosts);
        }

        /// <summary>
        /// Returns a collection containing the number of posts specified, picked at random
        /// from the supplied collection
        /// </summary>
        /// <param name="original">Original collection of posts to select from</param>
        /// <param name="numberOfPosts">Number of posts to return</param>
        /// <returns>Collection of Posts randomly selected from the supplied collection</returns>
        /// <remarks>
        /// If a PostCollection contains only 1 item, this is returned straight away.
        /// Usage:
        /// <code>#set($fiveRandomPosts = $ChalkExtensions.Randomize(posts, 5))</code>
        /// </remarks>
        public PostCollection Randomize(PostCollection original, int numberOfPosts)
        {
            if ((original == null) || (original.Count <= 1))
            {
                return original;
            }

            int safeNumberOfItemsToReturn = ((original.Count) > numberOfPosts) ? numberOfPosts : (original.Count);
            List<Post> posts = new List<Post>(safeNumberOfItemsToReturn);
            List<Post> copyOfOriginal = new List<Post>(original);
            Random random = new Random(Guid.NewGuid().GetHashCode());

            for (int i = 0; i < safeNumberOfItemsToReturn; i++)
            {
                int index = random.Next(0, (copyOfOriginal.Count - 1));
                posts.Add(copyOfOriginal[index]);
                copyOfOriginal.RemoveAt(index);
            }

            PostCollection postsToReturn = new PostCollection();

            posts.ForEach(delegate(Post post) { postsToReturn.Add(post); });

            return postsToReturn;
        }

        /// <summary>
        /// Sorts the collection of posts, by the propertyName supplied in ascending order
        /// </summary>
        /// <param name="posts">Collection of posts to sort</param>
        /// <param name="propertyName">Name of property to sort by</param>
        /// <returns>Collection of Posts sorted by the Property Name supplied in ascending order</returns>
        /// <remarks>
        /// Usage:
        /// <code>#set($sortedPosts = $ChalkExtensions.SortPostsAscending($ChalkExtensions.GetAllPostsForCategory("Archive"), "Title"))</code>
        /// </remarks>
        public PostCollection SortPostsAscending(PostCollection posts, string propertyName)
        {
            PostSorter sorter = new PostSorter(propertyName, PostSorter.SortDirection.Ascending);

            posts.Sort(sorter.Compare);

            return posts;
        }

        /// <summary>
        /// Sorts the collection of posts, by the propertyName supplied in descending order
        /// </summary>
        /// <param name="posts">Collection of posts to sort</param>
        /// <param name="propertyName">Name of property to sort by</param>
        /// <returns>Collection of Posts sorted by the Property Name supplied in descending order</returns>
        /// <remarks>
        /// Usage:
        /// <code>#set($sortedPosts = $ChalkExtensions.SortPostsDescending($ChalkExtensions.GetAllPostsForCategory("Archive"), "Title"))</code>
        /// </remarks>
        public PostCollection SortPostsDescending(PostCollection posts, string propertyName)
        {
            PostSorter sorter = new PostSorter(propertyName, PostSorter.SortDirection.Descending);

            posts.Sort(sorter.Compare);

            return posts;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="posts"></param>
        /// <returns></returns>
        private string GetCacheKey(PostCollection posts)
        {
            StringBuilder sb = new StringBuilder();

            if (posts != null)
            {
                foreach (Post post in posts)
                {
                    sb.Append(post.Id);
                    sb.Append("|");
                }
            }
            else
            {
                sb.Append("null");
            }

            return sb.ToString();
        }

        /// <summary>
        /// Retrieves the next or previous post in the category
        /// </summary>
        /// <param name="categoryName">Category to retrieve post from</param>
        /// <param name="currentPost">Current Post</param>
        /// <param name="retrieveNextPost">Whether next of previous post should be retrieved</param>
        /// <returns>The next post in the specified category collection, to the post specified</returns>
        private Post GetNextPreviousPostForCategory(string categoryName, Post currentPost, bool retrieveNextPost)
        {
            Post cachedPost = ZCache.Get<Post>(string.Format("ChalkExtensions::GetNextPreviousPostForCategory::categoryName:{0}:PostId{1}:retrieveNextPost{2}", categoryName, currentPost.Id, retrieveNextPost ));

            if (cachedPost == null)
            {
                PostCollection posts = new PostCollection();
                int postItemIndex = -1;

                Data data = new Data();
                Category category = data.GetCategory(categoryName);

                if (category != null)
                {
                    Query query = PostCollection.DefaultQuery(category.SortOrder);
                    query.AndWhere(Post.Columns.IsPublished, 1);

                    if (Category.IncludeChildPosts)
                    {
                        if (category.ParentId > 0)
                        {
                            query.AndWhere(Post.Columns.CategoryId, category.Id);
                        }
                        else
                        {
                            List<int> list = new List<int>(category.Children.Count + 1);

                            category.Children.ForEach(delegate(Category childCategory) { list.Add(childCategory.Id); });

                            list.Add(category.Id);

                            query.AndInWhere(Post.Columns.CategoryId, list.ToArray());
                        }
                    }
                    else
                    {
                        query.AndWhere(Post.Columns.CategoryId, category.Id);
                    }

                    posts.LoadAndCloseReader(query.ExecuteReader());

                    for (int i = 0; i < posts.Count; i++)
                    {
                        if (posts[i].Id == currentPost.Id)
                        {
                            if (retrieveNextPost)
                            {
                                postItemIndex = i + 1;
                                break;
                            }
                            else
                            {
                                postItemIndex = i - 1;
                                break;
                            }
                        }
                    }
                }
                else
                {
                    cachedPost = null;
                }

                if (postItemIndex >= 0 && postItemIndex < posts.Count)
                {
                    cachedPost = posts[postItemIndex];
                }
                else
                {
                    cachedPost = null;
                }

                if (cachedPost != null)
                {
                    ZCache.InsertCache(string.Format("ChalkExtensions::GetNextPreviousPostForCategory::categoryName:{0}:PostId{1}:retrieveNextPost{2}", categoryName, currentPost.Id, retrieveNextPost ), cachedPost, 60);
                }
            }

            return cachedPost;
        }

        #endregion
    }
}